--[[
    这份文件是游戏中所有特效制作的统一文件，如果某个特效需要专门的特殊处理，且未能在该文件内找到，请移步TS_HOOK_prefabs.lua
    特效生成时会将inst作为fx_spawned的事件推给world
]]



--仿照的官方写法
--主体部分为两个函数，startfx和fn
--其中fn是用于在专服上生成的轻量级代理实体，startfx则是真正的特效实体生成，这样做的唯一目的是减少专服的负担，fn生成的代理实体仅仅只是作为触发器，会在一秒后销毁
--这样的好处是。作为服务端的fn只负责记录特效的触发位置和时机，不用渲染，而作为负责客户端的startfx只需要专心渲染特效表现



local function PlaySound(inst, sound)
    inst.SoundEmitter:PlaySound(sound)
end


local function MakeFx(t)
    local assets =
    {
        Asset("ANIM",t.animatlas)
    }

    local function startfx(proxy)--参数为代理实体

        local inst = CreateEntity(t.name)

        --添加位置组件
        inst.entity:AddTransform()
        --添加动画组件
        inst.entity:AddAnimState()

        --获取代理实体的父类实体
        local parent = proxy.entity:GetParent() 
        if parent ~= nil then
            --设置特效实体的父级为代理实体的父类实体
            inst.entity:SetParent(parent.entity) 
        end

        if t.nameoverride == nil and t.description == nil then
            inst:AddTag("FX") --添加特效标签
        end
        --[[Non-networked entity]]
        --设置特效实体不能睡眠，这里的睡眠是告诉系统，引擎优化性能时不能暂停该实体的更新，因为特效实体需要一直更新保证动画的渲染
        inst.entity:SetCanSleep(false) 
        --设置特效实体不会被保存，直观地表现就是上下线特效会消失
        inst.persists = false 

        --设置特效实体的位置为代理实体的位置
        inst.Transform:SetFromProxy(proxy.GUID) 

        --父实体存在时，autorotate属性会让特效跟随父实体一直转向
        if t.autorotate and parent ~= nil then  
            inst.Transform:SetRotation(parent.Transform:GetRotation())
        end

        --如果设置了声音属性
        if t.sound ~= nil then  
            --添加声音组件
            inst.entity:AddSoundEmitter() 
            --在指定时间后播放声音，注意DoStaticTaskInTime的定时任务即使在暂停时也会执行，t.sound在游戏的设计中是不定参数的一部分，会传递给fn参数，也就是PlaySound的参数
            if t.update_while_paused then
                inst:DoStaticTaskInTime(t.sounddelay or 0, PlaySound, t.sound) 
            else
                --在指定时间后播放声音，注意DoTaskInTime的定时任务在暂停时不会执行
                inst:DoTaskInTime(t.sounddelay or 0, PlaySound, t.sound) 
            end
        end

        --从这里可以看出官方的特效支持同时播放两种声音
        if t.sound2 ~= nil then 
            --添加声音组件
            if inst.SoundEmitter == nil then
                inst.entity:AddSoundEmitter()
            end
            if t.update_while_paused then
                inst:DoStaticTaskInTime(t.sounddelay2 or 0, PlaySound, t.sound2)
            else
                inst:DoTaskInTime(t.sounddelay2 or 0, PlaySound, t.sound2)
            end
        end

        --设置动画组件的bank属性
        inst.AnimState:SetBank(t.bank) 
        --设置动画组件的build属性
        inst.AnimState:SetBuild(t.build) 
        -- THIS IS A CLIENT SIDE FUNCTION，猜测是为了支持动态配置动画，全局函数FunctionOrValue会在参数是函数时执行函数并返回结果，否则返回参数本身

        if t.shouldplayanim then
            inst.AnimState:PlayAnimation(FunctionOrValue(t.anim)) 
        end
        
        --在暂停时继续播放动画
        if t.update_while_paused then
            inst.AnimState:AnimateWhilePaused(true) 
        end
        
        --设置颜色的属性，tint应该是个v3相量，由于RGB本身就是三维向量，官方为了方便就直接使用了V3来计算，并且本身颜色就是可计算的
        if t.tint ~= nil then  
            inst.AnimState:SetMultColour(t.tint.x, t.tint.y, t.tint.z, t.tintalpha or 1) 
        --如果只有透明度选项，代表只需要设置透明度
        elseif t.tintalpha ~= nil then  
            inst.AnimState:SetMultColour(1, 1, 1, t.tintalpha)
        end

        if t.transform ~= nil then
            --设置缩放，同样transfer也是V3向量
            inst.AnimState:SetScale(t.transform:Get())
        end

        --如果设置了名称，代表特效可以用鼠标点击显示名字
        if t.nameoverride ~= nil then  
            if inst.components.inspectable == nil then
                --添加检查组件
                inst:AddComponent("inspectable")    
            end
            --设置显示的名称
            inst.components.inspectable.nameoverride = t.nameoverride 
            --设置inst的prefab名
            inst.name = t.nameoverride 
        end

        --设置描述
        if t.description ~= nil then    
            if inst.components.inspectable == nil then
                --添加检查组件
                inst:AddComponent("inspectable")
            end
            --设置检查时显示的语句，注意这里是函数，参数是inst和viwer，人物检查物品时优先级为getspecialdescription（特殊描述函数） -descriptionfn （普通描述函数）- description（静态描述文本，也就是STRING里面设置的检查语句）
            inst.components.inspectable.descriptionfn = t.description 
        end

        --是否启用辉光效果，Bloom是一种图形渲染特效，让明亮的区域产生发光/辉光的视觉效果，模拟现实中强光照射到镜头时的光晕现象。
        if t.bloom then 
            --设置辉光效果的着色器，这边用的是官方默认的着色器，里面是一些视觉上的算法之类，不懂的话就用官方的
            --需要注意的是，启用辉光效果会让特效变得更加逼真，但是会增加系统负担，占用性能，不建议大量使用
            inst.AnimState:SetBloomEffectHandle(t.shaders or "shaders/anim.ksh") 
        end

        --控制动画销毁的时机，是动画结束时销毁还是动画队列结束时销毁
        if not t.noendinganim then -- 只有在没有设置noendinganim时才自动移除
            if t.animqueue then  
                inst:ListenForEvent("animqueueover", inst.Remove)
            else
                inst:ListenForEvent("animover", inst.Remove)
            end
        end

        --额外执行的函数，注意这里函数环境是客户端
        if t.fn ~= nil then  
            if t.fntime ~= nil then
                if t.update_while_paused then
                    --根据update_while_paused属性延迟执行自定义函数，会将服务端的代理实体作为参数传递给自定义函数
                    inst:DoStaticTaskInTime(t.fntime, t.fn, proxy)
                else
                    inst:DoTaskInTime(t.fntime, t.fn, proxy)
                end
            else
                --从这可以看出自定义函数的两个参数，分别是特效实体和代理实体
                t.fn(inst, proxy)
            end
        end

        if TheWorld then
            --推动事件，把特效实体作为参数传递给TheWorld
            TheWorld:PushEvent("fx_spawned", inst)
        end
    end


    --从这开始创建服务端上的代理实体，也就是逻辑实体
    local function fn()  
        local inst = CreateEntity()

        --添加空间变换组件
        inst.entity:AddTransform() 
        --添加网络组件
        inst.entity:AddNetwork() 

        
        if t.fnsever ~= nil then
            t.fnsever(inst)
        end

        --Dedicated server does not need to spawn the local fx
        if not TheNet:IsDedicated() then
            --Delay one frame so that we are positioned properly before starting the effect
            --or in case we are about to be removed
            if t.update_while_paused then
                inst:DoStaticTaskInTime(0, startfx, inst)
            else
                inst:DoTaskInTime(0, startfx, inst)
            end
        end

        --设置特效有几个面，默认是四面，即正背侧（左右两侧）
        if t.twofaced then  
            inst.Transform:SetTwoFaced()
        elseif t.eightfaced then
            --设置特效有八个面
            inst.Transform:SetEightFaced()
        elseif t.sixfaced then
            --设置特效有六个面
            inst.Transform:SetSixFaced()
        elseif not t.nofaced then
            --设置特效有四个面
            inst.Transform:SetFourFaced()
        end

        --添加特效标签
        inst:AddTag("FX") 

        --这段代码千古难题，大概猜测就是通知系统可以开始网络同步了，一般要写在所有系统组件添加设置完毕之后，紧贴着判断主世界
        --在保证客户端渲染的同时，最大程度避免客户端执行游戏逻辑
        inst.entity:SetPristine() 
        
        if not TheWorld.ismastersim then
            return inst
        end

        --代理实体不应该保存
        inst.persists = false 
        --代理实体在完成客户端触发动画实体后就完成了任务，直接销毁节约性能
        inst:DoTaskInTime(1, inst.Remove)

        return inst
    end

    return Prefab(t.name, fn, assets)
end

local prefs = {}
local fx = require('main/TS_DATA').fx

for k, v in pairs(fx) do
    table.insert(prefs, MakeFx(v))
end

return unpack(prefs)
